@portabletext/markdown 1.4.6 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -77
- package/dist/index.d.ts +15 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +180 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,14 +72,14 @@ const markdown = portableTextToMarkdown([
|
|
|
72
72
|
| Unordered lists | ✅ | ✅ |
|
|
73
73
|
| Task lists | ✅\* | ✅\* |
|
|
74
74
|
| Nested lists | ✅ | ✅ |
|
|
75
|
-
| Code blocks | ✅ |
|
|
76
|
-
| Horizontal rules | ✅ |
|
|
77
|
-
| Images | ✅ |
|
|
78
|
-
| Tables |
|
|
79
|
-
| HTML blocks | ✅ |
|
|
80
|
-
| Callouts |
|
|
75
|
+
| Code blocks | ✅ | ✅ |
|
|
76
|
+
| Horizontal rules | ✅ | ✅ |
|
|
77
|
+
| Images | ✅ | ✅ |
|
|
78
|
+
| Tables | ✅ | ✅ |
|
|
79
|
+
| HTML blocks | ✅ | ✅ |
|
|
80
|
+
| Callouts | ✅ | ✅ |
|
|
81
81
|
|
|
82
|
-
\* Requires
|
|
82
|
+
\* Requires a schema that declares a `task` list; the default schema does not (see [GFM task lists](#configuring-matchers) below)
|
|
83
83
|
|
|
84
84
|
## Usage
|
|
85
85
|
|
|
@@ -162,14 +162,14 @@ Out of the box, the library includes sensible defaults for both. Customize them
|
|
|
162
162
|
|
|
163
163
|
The default schema includes the following definitions:
|
|
164
164
|
|
|
165
|
-
| Type | Values
|
|
166
|
-
| --------------- |
|
|
167
|
-
| `styles` | `'normal'`, `'h1'`, `'h2'`, `'h3'`, `'h4'`, `'h5'`, `'h6'`, `'blockquote'`
|
|
168
|
-
| `lists` | `'number'`, `'bullet'`
|
|
169
|
-
| `decorators` | `'strong'`, `'em'`, `'code'`, `'strike-through'`
|
|
170
|
-
| `annotations` | `'link'` (fields: `'href'`, `'title'`)
|
|
171
|
-
| `blockObjects` | `'code'` (fields: `'language'`, `'code'`), `'image'` (fields: `'src'`, `'alt'`, `'title'`), `'horizontal-rule'`, `'html'` (fields: `'html'`), `'table'` (
|
|
172
|
-
| `inlineObjects` | `'image'` (fields: `'src'`, `'alt'`, `'title'`)
|
|
165
|
+
| Type | Values |
|
|
166
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
167
|
+
| `styles` | `'normal'`, `'h1'`, `'h2'`, `'h3'`, `'h4'`, `'h5'`, `'h6'`, `'blockquote'` |
|
|
168
|
+
| `lists` | `'number'`, `'bullet'` |
|
|
169
|
+
| `decorators` | `'strong'`, `'em'`, `'code'`, `'strike-through'` |
|
|
170
|
+
| `annotations` | `'link'` (fields: `'href'`, `'title'`) |
|
|
171
|
+
| `blockObjects` | `'code'` (fields: `'language'`, `'code'`), `'image'` (fields: `'src'`, `'alt'`, `'title'`), `'horizontal-rule'`, `'html'` (fields: `'html'`), `'table'` (the canonical nested shape, see [Default behavior](#default-behavior)), `'callout'` (fields: `'tone'`, `'content'`) |
|
|
172
|
+
| `inlineObjects` | `'image'` (fields: `'src'`, `'alt'`, `'title'`) |
|
|
173
173
|
|
|
174
174
|
To use a custom Schema, import `compileSchema` and `defineSchema` from `@portabletext/schema`:
|
|
175
175
|
|
|
@@ -218,8 +218,38 @@ Matchers map Markdown concepts to Portable Text types defined in the Schema. Eac
|
|
|
218
218
|
| | `image` | `` | `'image'` |
|
|
219
219
|
| | `html` | HTML blocks | `'html'` |
|
|
220
220
|
| | `callout` | `> [!NOTE]`, etc. | `'callout'` |
|
|
221
|
-
| | `
|
|
222
|
-
| | `
|
|
221
|
+
| | `table` | GFM pipe tables | `'table'` |
|
|
222
|
+
| | `blockquote`\* | `>` blockquotes | `'blockquote'` |
|
|
223
|
+
| | `list`\* | `- ` or `1. ` lists | `'list'` |
|
|
224
|
+
|
|
225
|
+
\* Opt-in, not registered by default: `blockquote` and `list` map onto structural container shapes, and the parser only produces those shapes when you register the matcher (see [Configuring matchers](#configuring-matchers)). Without them, blockquotes and lists parse to flat text blocks, which is the standard Portable Text shape for both, not a degraded fallback.
|
|
226
|
+
|
|
227
|
+
#### Default behavior
|
|
228
|
+
|
|
229
|
+
**Tables** (GFM pipe tables) convert by default, in the canonical shape `@portabletext/plugin-table` expects: a `table` block object (`headerRows`, `rows`), each row a `row` object (`cells`), each cell a `cell` object (`value`, an array of Portable Text blocks; a cell holding a single image becomes a standalone block-level `image` object instead of a text block wrapping it). `alignment` is a `@portabletext/markdown` extension field; `@portabletext/plugin-table` ignores it. This needs no configuration when the schema declares a `table` block object with a `rows` field (see `blockObjects` above). A schema whose `table` doesn't declare `rows`, or that doesn't declare `table` at all, produces no table object at all: the table's cell content flattens into top-level blocks, in reading order, and the table structure is discarded.
|
|
230
|
+
|
|
231
|
+
**Images** are handled based on context:
|
|
232
|
+
|
|
233
|
+
- Standalone images (a paragraph containing only an image) become block-level `'image'` objects
|
|
234
|
+
- Images mixed with text become inline `'image'` objects (if the schema includes `'image'` in `inlineObjects`)
|
|
235
|
+
- If neither is supported, falls back to plain text: ``
|
|
236
|
+
|
|
237
|
+
The default image matcher requires the schema type to have a `'src'` field. If your `'image'` type doesn't include this field, the matcher returns `undefined`.
|
|
238
|
+
|
|
239
|
+
**Code** is handled based on the Markdown syntax:
|
|
240
|
+
|
|
241
|
+
- Fenced code blocks (` ``` `) become `'code'` block objects with `language` and `code` fields
|
|
242
|
+
- Inline code (`` ` ``) applies the `'code'` decorator to a span
|
|
243
|
+
|
|
244
|
+
The default code block matcher requires the schema type to have a `'code'` field. If your `'code'` type doesn't include this field, the matcher returns `undefined`.
|
|
245
|
+
|
|
246
|
+
**Links** support optional titles using `[text](url "title")` syntax. The title is captured in the `'title'` field of the `'link'` annotation.
|
|
247
|
+
|
|
248
|
+
**Nested lists** are handled automatically. Each list item block includes a `level` property indicating its nesting depth (1 for top-level, 2 for nested, etc.).
|
|
249
|
+
|
|
250
|
+
**HTML blocks** (like `<div>...</div>`) become `'html'` block objects with the raw HTML in the `'html'` field. Inline HTML is controlled by the `html.inline` option.
|
|
251
|
+
|
|
252
|
+
**Callouts** use the `> [!TYPE]` syntax (GFM alerts) where `TYPE` is one of `NOTE`, `TIP`, `WARNING`, `CAUTION`, or `IMPORTANT`. They become `'callout'` block objects with a `'tone'` field (the lowercased type name) and a `'content'` field (an array of Portable Text blocks). When the schema doesn't include a `'callout'` block object, the content falls back to blockquote-styled blocks.
|
|
223
253
|
|
|
224
254
|
#### Configuring matchers
|
|
225
255
|
|
|
@@ -246,7 +276,7 @@ markdownToPortableText(markdown, {
|
|
|
246
276
|
|
|
247
277
|
> **Note:** Checking if the type exists in the schema isn't required, but it's good practice. Returning `undefined` gracefully skips unsupported types.
|
|
248
278
|
|
|
249
|
-
**Table matcher:**
|
|
279
|
+
**Table matcher:** GFM pipe tables convert by default (see [Default behavior](#default-behavior)). Provide your own matcher to map onto a differently-shaped `table` type:
|
|
250
280
|
|
|
251
281
|
```ts
|
|
252
282
|
markdownToPortableText(markdown, {
|
|
@@ -348,31 +378,6 @@ Matchers receive:
|
|
|
348
378
|
|
|
349
379
|
Return `undefined` to skip the element (e.g., if the type isn't in the schema).
|
|
350
380
|
|
|
351
|
-
#### Default behavior for images and code
|
|
352
|
-
|
|
353
|
-
**Images** are handled based on context:
|
|
354
|
-
|
|
355
|
-
- Standalone images (a paragraph containing only an image) become block-level `'image'` objects
|
|
356
|
-
- Images mixed with text become inline `'image'` objects (if the schema includes `'image'` in `inlineObjects`)
|
|
357
|
-
- If neither is supported, falls back to plain text: ``
|
|
358
|
-
|
|
359
|
-
The default image matcher requires the schema type to have a `'src'` field. If your `'image'` type doesn't include this field, the matcher returns `undefined`.
|
|
360
|
-
|
|
361
|
-
**Code** is handled based on the Markdown syntax:
|
|
362
|
-
|
|
363
|
-
- Fenced code blocks (` ``` `) become `'code'` block objects with `language` and `code` fields
|
|
364
|
-
- Inline code (`` ` ``) applies the `'code'` decorator to a span
|
|
365
|
-
|
|
366
|
-
The default code block matcher requires the schema type to have a `'code'` field. If your `'code'` type doesn't include this field, the matcher returns `undefined`.
|
|
367
|
-
|
|
368
|
-
**Links** support optional titles using `[text](url "title")` syntax. The title is captured in the `'title'` field of the `'link'` annotation.
|
|
369
|
-
|
|
370
|
-
**Nested lists** are handled automatically. Each list item block includes a `level` property indicating its nesting depth (1 for top-level, 2 for nested, etc.).
|
|
371
|
-
|
|
372
|
-
**HTML blocks** (like `<div>...</div>`) become `'html'` block objects with the raw HTML in the `'html'` field. Inline HTML is controlled by the `html.inline` option.
|
|
373
|
-
|
|
374
|
-
**Callouts** use the `> [!TYPE]` syntax (GFM alerts) where `TYPE` is one of `NOTE`, `TIP`, `WARNING`, `CAUTION`, or `IMPORTANT`. They become `'callout'` block objects with a `'tone'` field (the lowercased type name) and a `'content'` field (an array of Portable Text blocks). When the schema doesn't include a `'callout'` block object, the content falls back to blockquote-styled blocks.
|
|
375
|
-
|
|
376
381
|
#### Other options
|
|
377
382
|
|
|
378
383
|
```ts
|
|
@@ -449,34 +454,42 @@ The conversion is driven by **Renderers**: functions that render Portable Text e
|
|
|
449
454
|
|
|
450
455
|
#### Default renderers
|
|
451
456
|
|
|
452
|
-
| Group | Renderer
|
|
453
|
-
| ------------------- |
|
|
454
|
-
| `
|
|
455
|
-
| | `
|
|
456
|
-
| | `
|
|
457
|
-
| `
|
|
458
|
-
| | `
|
|
459
|
-
| | `
|
|
460
|
-
|
|
|
461
|
-
| | `
|
|
462
|
-
| | `
|
|
463
|
-
| `
|
|
464
|
-
| `
|
|
465
|
-
| `
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
468
|
-
| `
|
|
469
|
-
| `
|
|
457
|
+
| Group | Renderer | Renders | Output |
|
|
458
|
+
| ------------------- | ----------------- | --------------------------------- | ------------------------ |
|
|
459
|
+
| `types` | `callout` | `callout` block objects | `> [!TYPE]\n> content` |
|
|
460
|
+
| | `code` | `code` block objects | Fenced code block |
|
|
461
|
+
| | `horizontal-rule` | `horizontal-rule` block objects | `---` |
|
|
462
|
+
| | `html` | `html` block objects | Raw HTML |
|
|
463
|
+
| | `image` | `image` block/inline objects | `` |
|
|
464
|
+
| | `table` | `table` block objects | Markdown table |
|
|
465
|
+
| `block` | `normal` | Paragraphs | `{children}` |
|
|
466
|
+
| | `h1`–`h6` | Headings | `# `–`###### ` |
|
|
467
|
+
| | `blockquote` | Blockquotes | `> {children}` |
|
|
468
|
+
| `marks` | `strong` | Bold text | `**{children}**` |
|
|
469
|
+
| | `em` | Italic text | `_{children}_` |
|
|
470
|
+
| | `code` | Inline code | `` `{children}` `` |
|
|
471
|
+
| | `underline` | Underlined text | `<u>{children}</u>` |
|
|
472
|
+
| | `strike-through` | Strikethrough | `~~{children}~~` |
|
|
473
|
+
| | `link` | Links | `[{children}](url)` |
|
|
474
|
+
| `listItem` | | List items (bullet, number, task) | `- `, `1. `, or `- [x] ` |
|
|
475
|
+
| `hardBreak` | | Line breaks within blocks | ` \n` (two spaces) |
|
|
476
|
+
| `blockSpacing` | | Spacing between blocks | `\n\n`, `\n`, `\n>\n` |
|
|
477
|
+
| `unknownType` | | Unknown block types | JSON code block |
|
|
478
|
+
| `unknownBlockStyle` | | Unknown block styles | `{children}` |
|
|
479
|
+
| `unknownListItem` | | Unknown list item types | `- {children}` |
|
|
480
|
+
| `unknownMark` | | Unknown marks | `{children}` |
|
|
470
481
|
|
|
471
482
|
Unknown types render as JSON code blocks by default; unknown styles, list items, and marks pass through their children.
|
|
472
483
|
|
|
484
|
+
The default type renderers are collision-safe: because the serializer dispatches on the `_type` name alone, `code`, `html`, `image`, `callout`, and `table` fall back to the `unknownType` renderer (a JSON code block) when a value doesn't match the shape their renderer expects (say, your own differently-shaped `code` type); `horizontal-rule` has no shape to check and always renders `---`. Register your own `types.<name>` renderer to override how any of them serialize, or to handle a same-named type of a different shape.
|
|
485
|
+
|
|
473
486
|
> **Note:** The `underline` renderer is included for Portable Text that uses it, but there's no standard Markdown syntax for underline, so it renders as HTML.
|
|
474
487
|
|
|
475
488
|
#### Configuring renderers
|
|
476
489
|
|
|
477
490
|
Provide custom renderers to control how Portable Text renders to Markdown.
|
|
478
491
|
|
|
479
|
-
**Custom type renderers:** Render custom block types (objects in the blocks array):
|
|
492
|
+
**Custom type renderers:** Render custom block types (objects in the blocks array). A custom renderer under a default's name (see [Default renderers](#default-renderers)) replaces that default:
|
|
480
493
|
|
|
481
494
|
```ts
|
|
482
495
|
portableTextToMarkdown(blocks, {
|
|
@@ -500,31 +513,19 @@ portableTextToMarkdown(blocks, {
|
|
|
500
513
|
})
|
|
501
514
|
```
|
|
502
515
|
|
|
503
|
-
**Built-in type renderers:**
|
|
516
|
+
**Built-in type renderers:** the library exports every built-in type renderer, in case you want to compose one into a different renderer or reuse it under a different type name. Two of them are exported but not registered by default: `DefaultBlockquoteObjectRenderer` and `DefaultListRenderer` render the structural container shapes (`types.blockquote`/`types.list` on the parser side) and stay opt-in, since the parser only produces those shapes when a matcher is registered for them:
|
|
504
517
|
|
|
505
518
|
```ts
|
|
506
519
|
import {
|
|
507
520
|
DefaultBlockquoteObjectRenderer,
|
|
508
|
-
DefaultCalloutRenderer,
|
|
509
|
-
DefaultCodeBlockRenderer,
|
|
510
|
-
DefaultHorizontalRuleRenderer,
|
|
511
|
-
DefaultHtmlRenderer,
|
|
512
|
-
DefaultImageRenderer,
|
|
513
521
|
DefaultListRenderer,
|
|
514
|
-
DefaultTableRenderer,
|
|
515
522
|
portableTextToMarkdown,
|
|
516
523
|
} from '@portabletext/markdown'
|
|
517
524
|
|
|
518
525
|
portableTextToMarkdown(blocks, {
|
|
519
526
|
types: {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
'code': DefaultCodeBlockRenderer,
|
|
523
|
-
'horizontal-rule': DefaultHorizontalRuleRenderer,
|
|
524
|
-
'html': DefaultHtmlRenderer,
|
|
525
|
-
'image': DefaultImageRenderer,
|
|
526
|
-
'list': DefaultListRenderer,
|
|
527
|
-
'table': DefaultTableRenderer,
|
|
527
|
+
blockquote: DefaultBlockquoteObjectRenderer,
|
|
528
|
+
list: DefaultListRenderer,
|
|
528
529
|
},
|
|
529
530
|
})
|
|
530
531
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -268,6 +268,14 @@ interface PortableTextRendererOptions<T> {
|
|
|
268
268
|
* Index of a list item
|
|
269
269
|
*/
|
|
270
270
|
listIndex?: number | undefined;
|
|
271
|
+
/**
|
|
272
|
+
* How deeply the list item should be indented, starting at 0.
|
|
273
|
+
*
|
|
274
|
+
* This is not the same as the block's `level`. A list can start deeper than
|
|
275
|
+
* level 1, and can skip levels, neither of which Markdown can express, so each
|
|
276
|
+
* jump to a deeper level counts as a single step of nesting.
|
|
277
|
+
*/
|
|
278
|
+
listDepth?: number | undefined;
|
|
271
279
|
/**
|
|
272
280
|
* Whether or not this node is "inline" - ie as a child of a text block,
|
|
273
281
|
* alongside text spans, or a block in and of itself.
|
|
@@ -446,13 +454,13 @@ declare const DefaultImageRenderer: PortableTextTypeRenderer<{
|
|
|
446
454
|
/**
|
|
447
455
|
* Renders a Portable Text table block-object back to Markdown.
|
|
448
456
|
*
|
|
449
|
-
* The PT `headerRows` field decides the header. `headerRows
|
|
450
|
-
*
|
|
451
|
-
* row is emitted and every row goes in the body (that empty
|
|
452
|
-
* as `headerRows: 0` via `markdownToPortableText`).
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
457
|
+
* The PT `headerRows` field decides the header. Missing `headerRows` and
|
|
458
|
+
* `headerRows === 0` both render headerless: GFM has no headerless form, so
|
|
459
|
+
* an empty header row is emitted and every row goes in the body (that empty
|
|
460
|
+
* header reads back as `headerRows: 0` via `markdownToPortableText`).
|
|
461
|
+
* `headerRows >= 1` promotes `rows[0]` to the header. GFM allows exactly one
|
|
462
|
+
* header row, so header rows beyond the first flatten into the body, lossy,
|
|
463
|
+
* but the extra rows stay on the Portable Text side.
|
|
456
464
|
*
|
|
457
465
|
* Asymmetric tables (rows of varying cell counts) are widened to match
|
|
458
466
|
* the row with the most cells. Narrower rows are padded with empty cells
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../node_modules/.pnpm/@portabletext+types@4.0.2/node_modules/@portabletext/types/dist/index.d.ts","../src/from-portable-text/renderers/block-spacing.ts","../src/from-portable-text/types.ts","../src/from-portable-text/portable-text-to-markdown.ts","../src/from-portable-text/renderers/hard-break.ts","../src/from-portable-text/renderers/list-item.ts","../src/from-portable-text/renderers/style.ts","../src/from-portable-text/renderers/marks.ts","../src/from-portable-text/renderers/type.ts","../src/to-portable-text/matchers.ts","../src/to-portable-text/markdown-to-portable-text.ts"],"x_google_ignoreList":[0],"mappings":";;;;;;UAKU;;;;;EAKR;;;;;EAKA;;;;;;KAMG,uBAAuB;GACzB;;;;;;;;;;;;;UAaO,oBAAkB,UAAU,6BAA6B,4BAA4B,UAAU,cAAc,uBAAuB,kBAAkB,mBAAmB,wBAAwB,mBAAmB,kCAAkC;;;;;;;EAO9P;;;;;;EAMA;;;;;EAKA,UAAU;;;;;;EAMV,WAAW;;;;;EAKX,QAAQ;;;;;EAKR,WAAW;;;;EAIX;;;;;;;;;;;UAWQ,0BAA0B,UAAU,6BAA6B,4BAA4B,UAAU,cAAc,kBAAkB,mBAAmB,wBAAwB,mBAAmB,kCAAkC,KAAK,oBAAkB,GAAG,GAAG,GAAG;EAC/Q,UAAU;;;;;;KAMP;;;;;KAKA;;;;;;;UAOK;;;;GAIP;;;;;EAKD;;;;EAIA;;;;;;UAMQ;;;;EAIR;;;;EAIA;;;;EAIA;;;;;;EAMA;;;;;KCnIU,wBAAwB;EAClC,SAAS;EACT,MAAM;;;;;cAMK,6BAA6B;KCRrC,YAAY,kBAAkB,KAAK,eAAe,QACpD,KAAK,KAAK;;;;;;KAQD,qBAAqB,MAC/B,SAAS,4BAA4B;;;;;;KAQ3B,4BAA4B,qBAAqB;;;;;;KAOjD,+BACV,qBAAqB;;;;;;KAOX,yBAAyB,UAAU,sBAC7C,SAAS,gCAAgC;;;;KAM/B,yBAAyB,UAAU,sBAC7C,SAAS,gCAAgC;;;;;;;UAS1B;;;;;;;;;;;EAWf,OAAO,eAAe;;;;;;;EAQtB,OAAO,eAAe;;;;;;;;;EAUtB,OACI,YAAY,wBAAwB,yCACpC;;;;;;;;;EAUJ,UACI,YACE,0BACA,4CAEF;;;;;EAMJ;;;;;EAMA,aAAa;;;;;EAMb,aAAa,qBAAqB;;;;;EAMlC,mBAAmB,qBAAqB;;;;;EAMxC,iBAAiB,qBAAqB;;;;;;;UAQvB,4BAA4B;;;;EAI3C,OAAO;;;;EAKP;;;;EAKA;;;;;EAMA;;;;EAKA;;;;;;EAOA,YAAY;;;;;;;KAQF,gCAAgC,KAAK,KAC/C,4BAA4B;;;;;;UASb,gCACf,UAAU,cAAc;;;;EAKxB,QAAQ;;;;EAKR;;;;EAKA;;;;EAKA;;;;EAKA;;;;;;EAOA,YAAY;;;;;;KAOF;GACN;EAAuB;IACzB;KAEQ,cAAc,UAAU,aAClC,SAAS,aAAa;UAGP,aAAa;EAC5B,MAAM;EACN;EACA;EACA,YAAY;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../node_modules/.pnpm/@portabletext+types@4.0.2/node_modules/@portabletext/types/dist/index.d.ts","../src/from-portable-text/renderers/block-spacing.ts","../src/from-portable-text/types.ts","../src/from-portable-text/portable-text-to-markdown.ts","../src/from-portable-text/renderers/hard-break.ts","../src/from-portable-text/renderers/list-item.ts","../src/from-portable-text/renderers/style.ts","../src/from-portable-text/renderers/marks.ts","../src/from-portable-text/renderers/type.ts","../src/to-portable-text/matchers.ts","../src/to-portable-text/markdown-to-portable-text.ts"],"x_google_ignoreList":[0],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { compileSchema, defineSchema, isSpan, isTextBlock } from "@portabletext/schema";
|
|
1
|
+
import { compileSchema, defineSchema, isSpan, isTextBlock, isTypedObject } from "@portabletext/schema";
|
|
2
2
|
import { buildMarksTree, isPortableTextBlock, isPortableTextListItemBlock, isPortableTextToolkitSpan, isPortableTextToolkitTextNode, spanToPlainText } from "@portabletext/toolkit";
|
|
3
3
|
import { alert } from "@mdit/plugin-alert";
|
|
4
4
|
import markdownit from "markdown-it";
|
|
@@ -24,57 +24,74 @@ function randomKey(length) {
|
|
|
24
24
|
}
|
|
25
25
|
const schema = compileSchema(defineSchema({}));
|
|
26
26
|
/**
|
|
27
|
-
* Builds a map of list item `_key`s to their index
|
|
27
|
+
* Builds a map of list item `_key`s to their index, and a map of list item
|
|
28
|
+
* `_key`s to the depth they should be rendered at.
|
|
29
|
+
*
|
|
30
|
+
* The depth is not the same as the block's `level`. A list can start at a level
|
|
31
|
+
* deeper than 1, and can skip levels, but Markdown has no way to express either:
|
|
32
|
+
* indentation is relative to the list item above, and indenting a first item by
|
|
33
|
+
* four spaces or more makes it a code block rather than a list. So each jump to a
|
|
34
|
+
* deeper level counts as a single step of nesting, however many levels it spans.
|
|
28
35
|
*
|
|
29
36
|
* Mutates the blocks in place by adding a `_key` if necessary.
|
|
30
37
|
*/
|
|
31
38
|
function buildListIndexMap(blocks) {
|
|
32
|
-
let levelIndexMaps = /* @__PURE__ */ new Map(), listIndexMap = /* @__PURE__ */ new Map(),
|
|
39
|
+
let levelIndexMaps = /* @__PURE__ */ new Map(), listIndexMap = /* @__PURE__ */ new Map(), listDepthMap = /* @__PURE__ */ new Map(), levelStack = [];
|
|
40
|
+
function depthOf(level) {
|
|
41
|
+
let deepest = levelStack.at(-1);
|
|
42
|
+
for (; deepest !== void 0 && deepest > level;) levelStack.pop(), deepest = levelStack.at(-1);
|
|
43
|
+
return deepest !== level && levelStack.push(level), levelStack.length - 1;
|
|
44
|
+
}
|
|
45
|
+
let previousListItem;
|
|
33
46
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
34
47
|
let block = blocks.at(blockIndex);
|
|
35
48
|
if (block === void 0) continue;
|
|
36
49
|
if (block._key ||= defaultKeyGenerator(), !isTextBlock({ schema }, block)) {
|
|
37
|
-
levelIndexMaps.clear(), previousListItem = void 0;
|
|
50
|
+
levelIndexMaps.clear(), previousListItem = void 0, levelStack = [];
|
|
38
51
|
continue;
|
|
39
52
|
}
|
|
40
53
|
if (block.listItem === void 0 || block.level === void 0) {
|
|
41
|
-
levelIndexMaps.clear(), previousListItem = void 0;
|
|
54
|
+
levelIndexMaps.clear(), previousListItem = void 0, levelStack = [];
|
|
42
55
|
continue;
|
|
43
56
|
}
|
|
44
|
-
|
|
57
|
+
let depth = depthOf(block.level);
|
|
58
|
+
if (listDepthMap.set(block._key, depth), !previousListItem) {
|
|
45
59
|
let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map();
|
|
46
|
-
levelIndexMap.set(
|
|
60
|
+
levelIndexMap.set(depth, 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, 1), previousListItem = {
|
|
47
61
|
listItem: block.listItem,
|
|
48
|
-
|
|
62
|
+
depth
|
|
49
63
|
};
|
|
50
64
|
continue;
|
|
51
65
|
}
|
|
52
|
-
if (previousListItem.listItem === block.listItem && previousListItem.
|
|
66
|
+
if (previousListItem.listItem === block.listItem && previousListItem.depth < depth) {
|
|
53
67
|
let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map();
|
|
54
|
-
levelIndexMap.set(
|
|
68
|
+
levelIndexMap.set(depth, 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, 1), previousListItem = {
|
|
55
69
|
listItem: block.listItem,
|
|
56
|
-
|
|
70
|
+
depth
|
|
57
71
|
};
|
|
58
72
|
continue;
|
|
59
73
|
}
|
|
60
74
|
levelIndexMaps.forEach((levelIndexMap, listItem) => {
|
|
61
75
|
if (listItem === block.listItem) return;
|
|
62
|
-
let
|
|
63
|
-
levelIndexMap.forEach((_,
|
|
64
|
-
|
|
65
|
-
}),
|
|
66
|
-
levelIndexMap.delete(
|
|
76
|
+
let depthsToDelete = [];
|
|
77
|
+
levelIndexMap.forEach((_, existingDepth) => {
|
|
78
|
+
existingDepth >= depth && depthsToDelete.push(existingDepth);
|
|
79
|
+
}), depthsToDelete.forEach((depthToDelete) => {
|
|
80
|
+
levelIndexMap.delete(depthToDelete);
|
|
67
81
|
});
|
|
68
82
|
});
|
|
69
|
-
let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map(), levelCounter = levelIndexMap.get(
|
|
70
|
-
levelIndexMap.set(
|
|
83
|
+
let levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map(), levelCounter = levelIndexMap.get(depth) ?? 0;
|
|
84
|
+
levelIndexMap.set(depth, levelCounter + 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(block._key, levelCounter + 1), previousListItem = {
|
|
71
85
|
listItem: block.listItem,
|
|
72
|
-
|
|
86
|
+
depth
|
|
73
87
|
};
|
|
74
88
|
}
|
|
75
|
-
return
|
|
89
|
+
return {
|
|
90
|
+
listIndexMap,
|
|
91
|
+
listDepthMap
|
|
92
|
+
};
|
|
76
93
|
}
|
|
77
|
-
const createRenderNode = (renderers, listIndexMap) => {
|
|
94
|
+
const createRenderNode = (renderers, listIndexMap, listDepthMap) => {
|
|
78
95
|
function renderNode(options) {
|
|
79
96
|
let { node, index, isInline } = options;
|
|
80
97
|
return isPortableTextListItemBlock(node) ? renderListItem(node, index) : isPortableTextToolkitSpan(node) ? renderSpan(node) : isPortableTextBlock(node) ? renderBlock(node, index, isInline) : isPortableTextToolkitTextNode(node) ? renderText(node) : renderCustomBlock(node, index, isInline);
|
|
@@ -99,6 +116,7 @@ const createRenderNode = (renderers, listIndexMap) => {
|
|
|
99
116
|
value: node,
|
|
100
117
|
index,
|
|
101
118
|
listIndex: node._key ? listIndexMap.get(node._key) : void 0,
|
|
119
|
+
listDepth: node._key ? listDepthMap.get(node._key) : void 0,
|
|
102
120
|
isInline: !1,
|
|
103
121
|
renderNode,
|
|
104
122
|
children
|
|
@@ -164,8 +182,8 @@ function serializeBlock(options) {
|
|
|
164
182
|
/**
|
|
165
183
|
* @public
|
|
166
184
|
*/
|
|
167
|
-
const DefaultBlockSpacingRenderer = ({ current, next }) => isPortableTextListItemBlock(current) && isPortableTextListItemBlock(next) ? "\n" : isPortableTextBlock(current) && isPortableTextBlock(next) && current.style === "blockquote" && next.style === "blockquote" ? "\n>\n" : "\n\n", DefaultHardBreakRenderer = () => " \n", DefaultListItemRenderer = ({ children, value, listIndex }) => {
|
|
168
|
-
let listStyle = value.listItem || "bullet",
|
|
185
|
+
const DefaultBlockSpacingRenderer = ({ current, next }) => isPortableTextListItemBlock(current) && isPortableTextListItemBlock(next) ? "\n" : isPortableTextBlock(current) && isPortableTextBlock(next) && current.style === "blockquote" && next.style === "blockquote" ? "\n>\n" : "\n\n", DefaultHardBreakRenderer = () => " \n", DefaultListItemRenderer = ({ children, value, listIndex, listDepth }) => {
|
|
186
|
+
let listStyle = value.listItem || "bullet", depth = listDepth ?? (value.level || 1) - 1, indent = " ".repeat(depth);
|
|
169
187
|
return listStyle === "number" ? `${indent}${listIndex ?? 1}. ${children}` : listStyle === "task" ? `${indent}- ${"checked" in value && typeof value.checked == "boolean" && value.checked ? "[x]" : "[ ]"} ${children}` : `${indent}- ${children}`;
|
|
170
188
|
}, DefaultUnknownListItemRenderer = ({ children }) => `- ${children}\n`;
|
|
171
189
|
/**
|
|
@@ -237,11 +255,75 @@ function uriLooksSafe(uri) {
|
|
|
237
255
|
/**
|
|
238
256
|
* @public
|
|
239
257
|
*/
|
|
240
|
-
const DefaultUnknownMarkRenderer = ({ children }) => children, DefaultNormalRenderer = ({ children }) => !children || children.trim() === "" ? "" : children, DefaultBlockquoteRenderer = ({ children }) => children ? children.split("\n").map((line) => `> ${line}`).join("\n") : ">", DefaultH1Renderer = ({ children }) => `# ${children}`, DefaultH2Renderer = ({ children }) => `## ${children}`, DefaultH3Renderer = ({ children }) => `### ${children}`, DefaultH4Renderer = ({ children }) => `#### ${children}`, DefaultH5Renderer = ({ children }) => `##### ${children}`, DefaultH6Renderer = ({ children }) => `###### ${children}`, DefaultUnknownStyleRenderer = ({ children }) => children ?? "", DefaultCodeBlockRenderer = (
|
|
241
|
-
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
258
|
+
const DefaultUnknownMarkRenderer = ({ children }) => children, DefaultNormalRenderer = ({ children }) => !children || children.trim() === "" ? "" : children, DefaultBlockquoteRenderer = ({ children }) => children ? children.split("\n").map((line) => `> ${line}`).join("\n") : ">", DefaultH1Renderer = ({ children }) => `# ${children}`, DefaultH2Renderer = ({ children }) => `## ${children}`, DefaultH3Renderer = ({ children }) => `### ${children}`, DefaultH4Renderer = ({ children }) => `#### ${children}`, DefaultH5Renderer = ({ children }) => `##### ${children}`, DefaultH6Renderer = ({ children }) => `###### ${children}`, DefaultUnknownStyleRenderer = ({ children }) => children ?? "", DefaultCodeBlockRenderer = (options) => isCodeShaped(options.value) ? `\`\`\`${normalizeLanguage(options.value.language)}\n${options.value.code}\n\`\`\`` : DefaultUnknownTypeRenderer(options);
|
|
259
|
+
function isCodeShaped(value) {
|
|
260
|
+
return typeof value?.code == "string";
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* A fence info string is everything after the opening fence on the same
|
|
264
|
+
* line, so a real `language` can never contain a newline, and the parser
|
|
265
|
+
* only ever produces a string. Junk in this optional field should not send
|
|
266
|
+
* an otherwise valid code block to the fenced-JSON path, so it is treated
|
|
267
|
+
* as absent instead of guarded.
|
|
268
|
+
*/
|
|
269
|
+
function normalizeLanguage(language) {
|
|
270
|
+
return typeof language != "string" || language.includes("\n") ? "" : language;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* @public
|
|
274
|
+
*/
|
|
275
|
+
const DefaultHorizontalRuleRenderer = () => "---", DefaultHtmlRenderer = (options) => isHtmlShaped(options.value) ? options.value.html : DefaultUnknownTypeRenderer(options);
|
|
276
|
+
function isHtmlShaped(value) {
|
|
277
|
+
return typeof value?.html == "string";
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* @public
|
|
281
|
+
*/
|
|
282
|
+
const DefaultImageRenderer = (options) => {
|
|
283
|
+
if (!isImageShaped(options.value)) return DefaultUnknownTypeRenderer(options);
|
|
284
|
+
let alt = escapeImageAndLinkText(options.value.alt ?? ""), title = options.value.title ? ` "${escapeImageAndLinkTitle(options.value.title)}"` : "";
|
|
285
|
+
return ``;
|
|
286
|
+
};
|
|
287
|
+
function isImageShaped(value) {
|
|
288
|
+
let image = value;
|
|
289
|
+
return typeof image?.src == "string" && (image.alt == null || typeof image.alt == "string") && (image.title == null || typeof image.title == "string");
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* A table is table-shaped when everything the renderer dereferences is
|
|
293
|
+
* there: `rows` an array of typed objects with a `cells` array, every cell
|
|
294
|
+
* a typed object whose `value` array holds typed objects (`renderNode`'s
|
|
295
|
+
* input contract). The predicate narrows to exactly what `renderTable`
|
|
296
|
+
* consumes, so the renderer needs no casts. A malformed `table` value
|
|
297
|
+
* (e.g. a consumer's differently-shaped `table` type) falls back to the
|
|
298
|
+
* fenced-JSON path instead of throwing.
|
|
299
|
+
*/
|
|
300
|
+
function isTableShaped(value) {
|
|
301
|
+
let rows = value?.rows;
|
|
302
|
+
return Array.isArray(rows) && rows.every((row) => isTypedObject(row) && Array.isArray(row.cells) && row.cells.every((cell) => isTypedObject(cell) && Array.isArray(cell.value) && cell.value.every(isTypedObject)));
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Renders a Portable Text table block-object back to Markdown.
|
|
306
|
+
*
|
|
307
|
+
* The PT `headerRows` field decides the header. Missing `headerRows` and
|
|
308
|
+
* `headerRows === 0` both render headerless: GFM has no headerless form, so
|
|
309
|
+
* an empty header row is emitted and every row goes in the body (that empty
|
|
310
|
+
* header reads back as `headerRows: 0` via `markdownToPortableText`).
|
|
311
|
+
* `headerRows >= 1` promotes `rows[0]` to the header. GFM allows exactly one
|
|
312
|
+
* header row, so header rows beyond the first flatten into the body, lossy,
|
|
313
|
+
* but the extra rows stay on the Portable Text side.
|
|
314
|
+
*
|
|
315
|
+
* Asymmetric tables (rows of varying cell counts) are widened to match
|
|
316
|
+
* the row with the most cells. Narrower rows are padded with empty cells
|
|
317
|
+
* so a GFM parser doesn't silently drop the extra cells in wider rows.
|
|
318
|
+
*
|
|
319
|
+
* @public
|
|
320
|
+
*/
|
|
321
|
+
const DefaultTableRenderer = (options) => {
|
|
322
|
+
let { value, renderNode } = options;
|
|
323
|
+
return isTableShaped(value) ? renderTable(value, renderNode) : DefaultUnknownTypeRenderer(options);
|
|
324
|
+
};
|
|
325
|
+
function renderTable(value, renderNode) {
|
|
326
|
+
let rows = value.rows, alignment = Array.isArray(value.alignment) ? value.alignment : void 0, headerRow = rows.at(0);
|
|
245
327
|
if (!headerRow) return "";
|
|
246
328
|
let getCellText = (cellBlocks) => cellBlocks.map((block, index) => renderNode({
|
|
247
329
|
node: block,
|
|
@@ -256,33 +338,55 @@ const DefaultUnknownMarkRenderer = ({ children }) => children, DefaultNormalRend
|
|
|
256
338
|
let align = alignment?.at(index);
|
|
257
339
|
return align === "left" ? " :--- " : align === "center" ? " :---: " : align === "right" ? " ---: " : " --- ";
|
|
258
340
|
}).join("|")}|`;
|
|
259
|
-
if (value.headerRows
|
|
260
|
-
lines.push(renderCells([])), lines.push(delimiter);
|
|
261
|
-
for (let row of rows) lines.push(renderRow(row.cells));
|
|
262
|
-
} else {
|
|
341
|
+
if ((Number(value.headerRows) || 0) >= 1) {
|
|
263
342
|
lines.push(renderRow(headerRow.cells)), lines.push(delimiter);
|
|
264
343
|
for (let i = 1; i < rows.length; i++) {
|
|
265
344
|
let row = rows.at(i);
|
|
266
345
|
row && lines.push(renderRow(row.cells));
|
|
267
346
|
}
|
|
347
|
+
} else {
|
|
348
|
+
lines.push(renderCells([])), lines.push(delimiter);
|
|
349
|
+
for (let row of rows) lines.push(renderRow(row.cells));
|
|
268
350
|
}
|
|
269
351
|
return lines.join("\n");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* @public
|
|
355
|
+
*/
|
|
356
|
+
const DefaultCalloutRenderer = (options) => {
|
|
357
|
+
if (!isCalloutShaped(options.value)) return DefaultUnknownTypeRenderer(options);
|
|
358
|
+
let { renderNode } = options, prefixed = options.value.content.map((block, index) => renderNode({
|
|
359
|
+
node: block._type === "block" ? {
|
|
273
360
|
...block,
|
|
274
361
|
style: "normal"
|
|
275
|
-
},
|
|
362
|
+
} : block,
|
|
276
363
|
index,
|
|
277
364
|
isInline: !1,
|
|
278
365
|
renderNode
|
|
279
366
|
})).join("\n\n").split("\n").map((line) => line === "" ? ">" : `> ${line}`).join("\n");
|
|
280
|
-
return `> [!${value.tone.toUpperCase()}]\n${prefixed}`;
|
|
281
|
-
}
|
|
282
|
-
|
|
367
|
+
return `> [!${options.value.tone.toUpperCase()}]\n${prefixed}`;
|
|
368
|
+
};
|
|
369
|
+
function isCalloutShaped(value) {
|
|
370
|
+
let callout = value;
|
|
371
|
+
return typeof callout?.tone == "string" && Array.isArray(callout.content) && callout.content.every(isTypedObject);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Renders a structural blockquote block-object (the `types.blockquote` shape
|
|
375
|
+
* produced by `markdownToPortableText` when a `types.blockquote` matcher is
|
|
376
|
+
* provided) back to Markdown. Each content block is rendered via the
|
|
377
|
+
* recursive renderer pipeline, joined with blank lines, and every line is
|
|
378
|
+
* prefixed with `> ` to form a Markdown blockquote.
|
|
379
|
+
*
|
|
380
|
+
* Distinct from `DefaultBlockquoteRenderer`, which renders flat-path text
|
|
381
|
+
* blocks with `style: 'blockquote'`.
|
|
382
|
+
*
|
|
383
|
+
* @public
|
|
384
|
+
*/
|
|
385
|
+
const DefaultBlockquoteObjectRenderer = ({ value, renderNode }) => value.content.map((block, index) => renderNode({
|
|
386
|
+
node: block._type === "block" ? {
|
|
283
387
|
...block,
|
|
284
388
|
style: "normal"
|
|
285
|
-
},
|
|
389
|
+
} : block,
|
|
286
390
|
index,
|
|
287
391
|
isInline: !1,
|
|
288
392
|
renderNode
|
|
@@ -314,7 +418,14 @@ const DefaultUnknownTypeRenderer = ({ value, isInline }) => {
|
|
|
314
418
|
let json = `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
|
|
315
419
|
return isInline ? `\n${json}\n` : json;
|
|
316
420
|
}, defaultRenderers = {
|
|
317
|
-
types: {
|
|
421
|
+
types: {
|
|
422
|
+
callout: DefaultCalloutRenderer,
|
|
423
|
+
code: DefaultCodeBlockRenderer,
|
|
424
|
+
"horizontal-rule": DefaultHorizontalRuleRenderer,
|
|
425
|
+
html: DefaultHtmlRenderer,
|
|
426
|
+
image: DefaultImageRenderer,
|
|
427
|
+
table: DefaultTableRenderer
|
|
428
|
+
},
|
|
318
429
|
block: {
|
|
319
430
|
normal: DefaultNormalRenderer,
|
|
320
431
|
blockquote: DefaultBlockquoteRenderer,
|
|
@@ -363,7 +474,7 @@ function portableTextToMarkdown(blocks, options = {}) {
|
|
|
363
474
|
unknownBlockStyle: options.unknownBlockStyle ?? defaultRenderers.unknownBlockStyle,
|
|
364
475
|
unknownListItem: options.unknownListItem ?? defaultRenderers.unknownListItem,
|
|
365
476
|
unknownMark: options.unknownMark ?? defaultRenderers.unknownMark
|
|
366
|
-
}, renderBlockSpacing = options.blockSpacing ?? DefaultBlockSpacingRenderer, listIndexMap = buildListIndexMap(blocks), renderNode = createRenderNode(renderers, listIndexMap);
|
|
477
|
+
}, renderBlockSpacing = options.blockSpacing ?? DefaultBlockSpacingRenderer, { listIndexMap, listDepthMap } = buildListIndexMap(blocks), renderNode = createRenderNode(renderers, listIndexMap, listDepthMap);
|
|
367
478
|
return blocks.map((node, index) => {
|
|
368
479
|
let renderedNode = renderNode({
|
|
369
480
|
node,
|
|
@@ -435,7 +546,24 @@ const normalStyleDefinition = { name: "normal" }, h1StyleDefinition = { name: "h
|
|
|
435
546
|
},
|
|
436
547
|
{
|
|
437
548
|
name: "rows",
|
|
438
|
-
type: "array"
|
|
549
|
+
type: "array",
|
|
550
|
+
of: [{
|
|
551
|
+
type: "object",
|
|
552
|
+
name: "row",
|
|
553
|
+
fields: [{
|
|
554
|
+
name: "cells",
|
|
555
|
+
type: "array",
|
|
556
|
+
of: [{
|
|
557
|
+
type: "object",
|
|
558
|
+
name: "cell",
|
|
559
|
+
fields: [{
|
|
560
|
+
name: "value",
|
|
561
|
+
type: "array",
|
|
562
|
+
of: [{ type: "block" }, { type: "image" }]
|
|
563
|
+
}]
|
|
564
|
+
}]
|
|
565
|
+
}]
|
|
566
|
+
}]
|
|
439
567
|
}
|
|
440
568
|
]
|
|
441
569
|
}, defaultCalloutObjectDefinition = {
|
|
@@ -546,6 +674,13 @@ const codeBlockMatcher = ({ context, value, isInline }) => {
|
|
|
546
674
|
isInline
|
|
547
675
|
});
|
|
548
676
|
if (imageObject && "src" in imageObject) return imageObject;
|
|
677
|
+
}, tableBlockMatcher = ({ context, value, isInline }) => {
|
|
678
|
+
let tableObject = buildObjectMatcher(defaultTableObjectDefinition)({
|
|
679
|
+
context,
|
|
680
|
+
value,
|
|
681
|
+
isInline
|
|
682
|
+
});
|
|
683
|
+
if (tableObject && "rows" in tableObject) return tableObject;
|
|
549
684
|
}, defaultOptions = {
|
|
550
685
|
schema: defaultSchema,
|
|
551
686
|
keyGenerator: defaultKeyGenerator,
|
|
@@ -577,7 +712,8 @@ const codeBlockMatcher = ({ context, value, isInline }) => {
|
|
|
577
712
|
horizontalRule: buildObjectMatcher(defaultHorizontalRuleObjectDefinition),
|
|
578
713
|
html: buildObjectMatcher(defaultHtmlObjectDefinition),
|
|
579
714
|
image: imageBlockMatcher,
|
|
580
|
-
callout: buildObjectMatcher(defaultCalloutObjectDefinition)
|
|
715
|
+
callout: buildObjectMatcher(defaultCalloutObjectDefinition),
|
|
716
|
+
table: tableBlockMatcher
|
|
581
717
|
}
|
|
582
718
|
};
|
|
583
719
|
/**
|